package com.common.util;

import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.hutool.core.date.DateUtil;

/**
 * 时间格式处理工具类<br>
 * @author ficus
 */
public final class DateTimeUtil {
    /**
     * 日志记录器.
     */
    private static Logger s_logger = LoggerFactory.getLogger(DateTimeUtil.class);

    private DateTimeUtil() {

    }

    private static final int FEBRUARY = 2;
    
    private static final int[] MONTH_LENGTH = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    public static final String AM_PM = "a";
    public static final String DAY_IN_MONTH = "dd";
    public static final String DAY_IN_YEAR = "DD";
    public static final String DAY_OF_WEEK = "EEEE";
    public static final int DIFF_DAY = 5;
    public static final int DIFF_HOUR = 11;
    public static final int DIFF_MILLSECOND = 14;
    public static final int DIFF_MINUTE = 12;
    public static final int DIFF_MONTH = 2;
    public static final int DIFF_SECOND = 13;
    public static final int DIFF_WEEK = 4;
    public static final int DIFF_YEAR = 1;
    public static final String HOUR_IN_APM = "KK";
    public static final String HOUR_IN_DAY = "HH";
    public static final String HOUR_OF_APM = "hh";
    public static final String HOUR_OF_DAY = "kk";
    public static final String LONG_YEAR = "yyyy";
    public static final String MILL_SECOND = "SSS";
    public static final String MINUTE = "mm";
    public static final String MONTH = "MM";
    public static final String SECOND = "ss";
    public static final String SHORT_YEAR = "yy";
    public static final String WEEK_IN_MONTH = "W";
    public static final String WEEK_IN_YEAR = "ww";
    public static final String yyyy_MM_dd = "yyyy-MM-dd";
    public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss";
    public static final String MMddHHmmss = "MMddHHmmss";
    public static final String yyyyMMdd = "yyyyMMdd";
    public static final String MMdd = "MMdd";
    public static final String yyyyMMddHHmmssSSS = "yyyyMMddHHmmssSSS";
    public static final String yyyy_MM = "yyyy-MM";
    public static final String yyyyMM = "yyyyMM";
    public static final String yyyy_MM_dd_HHmmssSSS = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String yyyy_MM_dd_HHmmss4 = "yyyy年MM月dd日HH:mm:ss";
    public static final String yyyy_MM_dd_HHmmss = "yyyyMMdd hh:mm:ss";
    public static final String yyyy_MM_dd_HHmmss2 = "yyyy-MM-dd HH:mm:ss";
    public static final String yyyy_MM_dd_HHmmss3 = "yyyy/MM/dd hh:mm:ss";
    public static final String yyyy_MM_dd_HH = "yyyy-MM-dd HH";
    public static final String HHmm = "HHmm";
    public static final String HHmmss = "HHmmss";
    public static final String mmssSSS = "mmssSSS";
    public static final String HHmm2 = "HH:mm";
    public static final String yyyyMMddd = "yyyy/MM/dd";
    public static final String HHmmsss = "HH:mm:ss";
    public static final String yyMMdd = "yyMMdd";
    public static final String yyyy = "yyyy";


    /**
     * 判断日期是否在范围内，包含相等的日期
     */
    public static boolean isBetween(final Date date, final Date start,final Date end) {
        if (date == null || start == null || end == null || start.after(end)) {
            throw new IllegalArgumentException("some date parameters is null or dateBein after dateEnd");
        }
        return !date.before(start) && !date.after(end);
    }
    
    /**
     * 获得日期是一周的第几天. 已改为中国习惯，1 是Monday，而不是Sundays.
     */
    public static int getDayOfWeek(final Date date) {
        int result = DateUtil.dayOfWeek(date);
        return result == 1 ? 7 : result - 1;
    }
    
    /**
     * 获得月份 从1-12
     */
    public static int getMonth(final Date date) {
        int result = DateUtil.month(date);
        return result + 1;
    }
    
    /**
     * 是否闰年.
     */
    public static boolean isLeapYear(final Date date) {
        return DateUtil.isLeapYear(DateUtil.year(date));
    }
    
    /**
     * 获取某个月有多少天, 考虑闰年等因数
     */
    public static int getMonthLength(final Date date) {
        int year = DateUtil.year(date);
        int month = getMonth(date);
        return getMonthLength(year, month);
    }

    /**
     * 获取某个月有多少天, 考虑闰年等因数, 移植Jodd Core的TimeUtil
     */
    public static int getMonthLength(int year, int month) {
        if ((month < 1) || (month > 12)) {
            throw new IllegalArgumentException("Invalid month: " + month);
        }
        if (month == FEBRUARY) {
            return DateUtil.isLeapYear(year) ? 29 : 28;
        }
        return MONTH_LENGTH[month];
    }
    
    public static Date buildStartDateTime(Date startDate) {
        ZoneId zoneId = ZoneId.systemDefault();
        // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime。
        LocalDate localDate = startDate.toInstant().atZone(zoneId).toLocalDate();
        LocalDateTime ldt2 = localDate.atTime(LocalTime.MIN);
        return Date.from(ldt2.atZone(zoneId).toInstant());
    }

    public static Date buildEndDateTime(Date endDate) {
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate localDate = endDate.toInstant().atZone(zoneId).toLocalDate();
        LocalDateTime ldt2 = localDate.atTime(LocalTime.MAX);
        return Date.from(ldt2.atZone(zoneId).toInstant());
    }


    public static void main(String[] args) {

        Date d1 = buildStartDateTime(new Date());
        System.out.println(d1);

        Date d2 = buildEndDateTime(new Date());
        System.out.println(d2);

        Date d3 = buildStartMillisecond(new Date());
        System.out.println(d3);

        Date d4 = buildEndMillisecond(new Date());
        System.out.println(d4);

        
        System.err.println(DateUtil.month(new Date()));
    }

    public static Date buildStartMillisecond(Date startDate) {
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate localDate = startDate.toInstant().atZone(zoneId).toLocalDate();
        LocalTime localTime = startDate.toInstant().atZone(zoneId).toLocalTime().withNano(000_000_000);
        LocalDateTime ldt2 = localDate.atTime(localTime);
        return Date.from(ldt2.atZone(zoneId).toInstant());
    }

    public static Date buildEndMillisecond(Date endDate) {
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate localDate = endDate.toInstant().atZone(zoneId).toLocalDate();
        LocalTime localTime = endDate.toInstant().atZone(zoneId).toLocalTime().withNano(999_999_999);
        LocalDateTime ldt2 = localDate.atTime(localTime);
        return Date.from(ldt2.atZone(zoneId).toInstant());
    }


    public static boolean compareElapsedTime(Date destDate, Date sourceDate, int type, int elapse)
            throws RuntimeException {
        if (destDate == null || sourceDate == null) {
            throw new RuntimeException("compared date invalid");
        }
        return destDate.getTime() > DateTimeUtil.getRelativeDate(sourceDate, type, elapse).getTime();
    }

    /**
     * 获得距离当天指定天数的日期
     *
     * @param day 间隔天数(负数提前,正数滞后)
     * @return
     * @title getDateFromToday
     */
    public static Date getDateFromToday(int day) {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.DATE, c.get(Calendar.DATE) + day);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return DateTimeUtil.strToDateLong(dateFormat.format(c.getTime()));
    }

    /**
     * 获得距离当天指定天数的日期 字符串
     *
     * @param day 间隔天数(负数提前,正数滞后)
     * @return
     * @title getDateStringFromToday
     */
    public static String getDateStringFromToday(int day) {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.DATE, c.get(Calendar.DATE) + day);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return dateFormat.format(c.getTime());
    }

    /**
     * 将长时间格式字符串转换为时间 yyyy-MM-dd
     *
     * @param strDate
     * @return
     */
    public static Date strToDateLong(String strDate) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        ParsePosition pos = new ParsePosition(0);
        Date strtodate = formatter.parse(strDate, pos);
        return strtodate;
    }

    /**
     * 获取当前日期字符串
     *
     * @return
     * @title getCurrentDateString
     */
    public static String getCurrentDateString() {
        return DateTimeUtil.getCurrentDateString("yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 按照指定格式字符串来，获取当前日期字符串值
     *
     * @param formatString 格式字符串
     * @return
     * @title getCurrentDateString
     * @date 2016年6月2日 liqiao created
     */
    public static String getCurrentDateString(String formatString) {
        Date currentDate = new Date();
        return DateTimeUtil.getDateString(currentDate, formatString);
    }

    /**
     * 将日期字符串转化为日期值
     *
     * @param strDate
     * @return
     * @title strToDate
     * @date 2016年6月2日 liqiao created
     */
    public static Date strToDate(String strDate) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        ParsePosition pos = new ParsePosition(0);
        Date strtodate = formatter.parse(strDate, pos);
        return strtodate;
    }

    /**
     * 将指定日期 2016-06-02 10:10:10日期值转化为2016-06-02日期值，即忽略时、分、秒
     *
     * @param date 指定日期
     * @return
     * @title getDate
     */
    public static Date getDate(Date date) {
        return DateTimeUtil.getDateFromString(DateTimeUtil.getDateString(date, "yyyy-MM-dd"), "yyyy-MM-dd");
    }

    /**
     * 将指定日期 2016-06-02 10:10:10日期值转化为按照指定格式串format，转化为日期值
     *
     * @param date
     * @param format
     * @return
     * @title getDateFormat
     */
    public static Date getDateFormat(Date date, String format) {
        return DateTimeUtil.getDateFromString(DateTimeUtil.getDateString(date, format), format);
    }

    /**
     * 将yyyy-MM-dd HH:mm:ss日期字符串转化为日期值
     *
     * @param dateString
     * @return
     * @throws RuntimeException
     * @title getDateFromString
     */
    public static Date getDateFromString(String dateString) throws RuntimeException {
        return DateTimeUtil.getDateFromString(dateString, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 将yyyyMMddHHmmss日期字符串值转化为Date
     *
     * @param dateString
     * @return
     * @throws RuntimeException
     * @title getDateFromStringyyyyMMddHHmmss
     */
    public static Date getDateFromStringyyyyMMddHHmmss(String dateString) throws RuntimeException {
        return DateTimeUtil.getDateFromString(dateString, "yyyyMMddHHmmss");
    }

    /**
     * 将dateString按照format来转化为日期Date
     *
     * @param dateString
     * @param format
     * @return
     * @throws RuntimeException
     * @title getDateFromString
     */
    public static Date getDateFromString(String dateString, String format) throws RuntimeException {
        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
        Date date = null;
        try {
            date = dateFormat.parse(dateString);
        } catch (ParseException e) {
            throw new RuntimeException((new StringBuilder("parse date string '")).append(dateString)
                    .append("' with pattern '").append(format).append("' failed: ").append(e.getMessage()).toString());
        }
        return date;
    }

    /**
     * 得到date的前N天,N为正数则为后几天,负数为前几天
     *
     * @param date
     * @param num
     * @return
     * @title getIntDay
     */
    public static Date getIntDay(Date date, int num) {
        if (date == null) {
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, num);
        date = calendar.getTime();
        return date;
    }

    /**
     * 得到date的yyyy-MM-dd HH:mm:ss的日期字符串值
     *
     * @param date
     * @return
     * @title getDateString
     */
    public static String getDateString(Date date) {
        return DateTimeUtil.getDateString(date, "yyyy-MM-dd HH:mm:ss");
    }

    public static String getDateStringByFormat(Date date, String format) {
        return DateTimeUtil.getDateString(date, format);
    }

    public static String getCurDateString() {
        return DateTimeUtil.getDateString(new Date(), DateTimeUtil.yyyyMMddHHmmssSSS);
    }

    public static String getDateString(Date date, String formatString) {
        return DateTimeUtil.getDateString(date, formatString, Locale.PRC);
    }

    public static String getDateString(Date date, String formatString, Locale locale) {
        if (date == null) {
            return null;
        } else {
            SimpleDateFormat dateFormat = new SimpleDateFormat(formatString, locale);
            return dateFormat.format(date);
        }
    }


    /**
     * @param date
     * @return int
     * @throws @author
     * @function:获取 某个日期的年份
     */
    public static int getYear(Date date) {
        if (date == null) {
            return DateTimeUtil.getYear();
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.YEAR);
    }

    /**
     * @return int
     * @throws @author
     * @function: 获取当前日期的年份
     */

    public static int getYear() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        return calendar.get(Calendar.YEAR);
    }

    public static int getDayOfMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(5);
    }

    public static int getDaysOfMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.getActualMaximum(5);
    }

    /**
     * @param date
     * @param type 取值 Calendar.type
     * @param relate
     * @return
     */
    public static Date getRelativeDate(Date date, int type, int relate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(type, relate);
        return calendar.getTime();
    }

    public static Date getRelativeDate(int type, int relate) {
        Date current = new Date();
        return DateTimeUtil.getRelativeDate(current, type, relate);
    }

    /**
     * 操作STRING类型的时间
     *
     * @param DAY    yyyy-MM-dd
     * @param type   DIFF_*
     * @param relate 天数
     * @return yyyy-MM-dd
     */
    public static String getRelativeStringDate(String DAY, int type, int relate) {
        return DateTimeUtil.getDateString(
                DateTimeUtil.getRelativeDate(DateTimeUtil.getDateFromString(DAY, DateTimeUtil.yyyy_MM_dd), type, relate),
                DateTimeUtil.yyyy_MM_dd);
    }

    public static String getRelativeDateString(int type, int relate, String formatString) {
        return DateTimeUtil.getDateString(DateTimeUtil.getRelativeDate(type, relate), formatString);
    }

    public static String getTimestampString(Date date) {
        return DateTimeUtil.getDateString(date, "yyyyMMddHHmmssSSS");
    }

    /**
     * 获取day
     *
     * @return
     * @title getToday
     */
    public static int getToday() {
        return Integer.parseInt(DateTimeUtil.getCurrentDateString("dd"));
    }

    public static long getTimeDiff(Date fromDate, Date toDate, int type) {
        fromDate = fromDate != null ? fromDate : new Date();
        toDate = toDate != null ? toDate : new Date();
        long diff = toDate.getTime() - fromDate.getTime();
        switch (type) {
            case 13:
                // '\r'
                diff /= 1000L;
                break;

            case 12:
                // '\f'
                diff /= 60000L;
                break;

            case 11:
                // '\013'
                diff /= 0x36ee80L;
                break;

            case 5:
                // '\005'
                diff /= 0x5265c00L;
                break;

            case 2:
                // '\002'
                diff /= 0xffffffff9a7ec800L;
                break;

            case 1:
                // '\001'
                diff /= 0x57b12c00L;
                break;

            case 3:
                // '\003'
            case 4:
                // '\004'
            case 6:
                // '\006'
            case 7:
                // '\007'
            case 8:
                // '\b'
            case 9:
                // '\t'
            case 10:
                // '\n'
            default:
                diff = 0L;
                break;

            case 14:
                // '\016'
                break;
        }
        return diff;
    }

    public static boolean isTimestampEqual(Date arg0, Date arg1) {
        return DateTimeUtil.getTimestampString(arg0).compareTo(DateTimeUtil.getTimestampString(arg1)) == 0;
    }

    /**
     * 设置起始时间，在params中显示，开始时间key为start，结束时间key为end，start和end皆为Date格式。
     * <p>
     * 如传入：pointH=0，pointM=0，pointS=0，interval=0，date为2013年5月13日（时分秒忽略）。
     * 则params的值：start为2013年5月12日00点00分00秒，end为2013年5月13日00点00分00秒
     * </p>
     * <p>
     * 如传入：pointH=23，pointM=30，pointS=30，interval=1，date为2013年5月13日（时分秒忽略）。
     * 则params的值：start为2013年5月10日23点30分30秒，end为2013年5月12日23点30分30秒
     * </p>
     *
     * @param params   参数
     * @param pointH   日切点：时（0~23）
     * @param pointM   日切点：分（0~59）
     * @param pointS   日切点：秒（0~59）
     * @param interval 所需要设置的开始时间和结束时间之间的间隔天数，默认为0（大于等于0）
     * @param date     日期，默认当前日期
     * @author
     * @date 2013-5-13
     */
    @SuppressWarnings("unchecked")
    public static void setParam(Map params, int pointH, int pointM, int pointS, int interval, Date date) {
        if (params == null) {
            return;
        }
        if (pointH < 0 || pointM < 0 || pointS < 0 || interval < 0 || pointH > 23 || pointM > 59 || pointS > 59) {
            return;
        }
        date = date == null ? new Date() : date;
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, pointH);
        cal.set(Calendar.MINUTE, pointM);
        cal.set(Calendar.SECOND, pointS);
        if (pointH == 0) {
            params.put("end", cal.getTime());
            cal.add(Calendar.DAY_OF_MONTH, -1 - interval);
            params.put("start", cal.getTime());
        } else {
            cal.add(Calendar.DAY_OF_MONTH, -1);
            params.put("end", cal.getTime());
            cal.add(Calendar.DAY_OF_MONTH, -1 - interval);
            params.put("start", cal.getTime());
        }
    }

    /**
     * 获取一个月的最后一天日期字符串值 ,格式:yyyy-MM-dd
     *
     * @param dateStr dateStr 格式:yyyy-MM-dd
     * @return
     * @title getEndDateOfMonth
     */
    public static String getEndDateOfMonth(String dateStr) {
        String dateStrPrefix = dateStr.substring(0, 8);
        String month = dateStr.substring(5, 7);
        int mon = Integer.parseInt(month);
        if (mon == 1 || mon == 3 || mon == 5 || mon == 7 || mon == 8 || mon == 10 || mon == 12) {
            dateStrPrefix += "31";
        } else if (mon == 4 || mon == 6 || mon == 9 || mon == 11) {
            dateStrPrefix += "30";
        } else {
            if (DateTimeUtil.isLeapYear(dateStr)) {
                dateStrPrefix += "29";
            } else {
                dateStrPrefix += "28";
            }
        }
        return dateStrPrefix;
    }

    /**
     * 判断是否润年
     *
     * @param dataStr
     * @return
     * @title isLeapYear
     */
    public static boolean isLeapYear(String dataStr) {
        /**
         * 详细设计： 1.被400整除是闰年，否则： 2.不能被4整除则不是闰年 3.能被4整除同时不能被100整除则是闰年 3.能被4整除同时能被100整除则不是闰年
         */
        Date d = DateTimeUtil.strToDate(dataStr);
        GregorianCalendar gc = (GregorianCalendar) Calendar.getInstance();
        gc.setTime(d);
        int year = gc.get(Calendar.YEAR);
        if ((year % 400) == 0) {
            return true;
        } else if ((year % 4) == 0) {
            if ((year % 100) == 0) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;

        }
    }

    /**
     * 取得指定月份的第一天
     *
     * @param date String
     * @return String
     */
    public static String getMonthBegin(String date) {
        String str = date.substring(0, 8);
        return str + "01";
    }

}
